//
// Created by martin on 2/9/22.
//

#include "SocketsOps.h"
#include "muduo/base/Types.h"
#include "muduo/base/Logging.h"
#include "muduo/net/Endian.h"

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <sys/uio.h>

using namespace muduo;
using namespace muduo::net;

namespace
{

typedef struct sockaddr SA;

//#if !VALGRIND || defined(NO_ACCEPT4) // FIXME for test
#if VALGRIND || defined(NO_ACCEPT4)
void setNonBlockAndCloseOnExec(int sockfd)
{
	// non-block
	int flags = ::fcntl(sockfd, F_GETFL, 0);
	flags |= O_NONBLOCK;
	int ret = ::fcntl(sockfd, F_SETFL, flags);
	// FIXME check
	if (ret < 0) {
		LOG_SYSERR << "fcntl add option O_NONBLOCK error";
		return;
	}

	// close-on-exec
	flags = ::fcntl(sockfd, F_GETFD, 0);
	flags |= FD_CLOEXEC;
	ret = ::fcntl(sockfd, F_SETFD, flags);
	// FIXME check
	if (ret < 0) {
		LOG_SYSERR << "fcntl add option FD_CLOEXEC error";
		return;
	}

	(void)ret;
}
#endif

} // namespace

const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in6* addr)
{
	//    reinterpret_cast<const struct sockaddr*>();
	//    return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
	return static_cast<const sockaddr*>(implicit_cast<const void*>(addr));
}

struct sockaddr* sockets::sockaddr_cast(struct sockaddr_in6* addr)
{
	return static_cast<struct sockaddr*>(implicit_cast<void*>(addr));
}

const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in* addr)
{
	return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}

const struct sockaddr_in* sockets::sockaddr_in_cast(const struct sockaddr* addr)
{
	return static_cast<const struct sockaddr_in*>(implicit_cast<const void*>(addr));
}

const struct sockaddr_in6* sockets::sockaddr_in6_cast(const struct sockaddr* addr)
{
	return static_cast<const struct sockaddr_in6*>(implicit_cast<const void*>(addr));
}

/**
 * 创建一个非阻塞 sock fd
 * @param family 协议族, 可取值AF_UNIX/AF_INET/AF_INET6 etc.
 * @return 成功, 返回sock fd; 失败, 程序终止(LOG_SYSFATAL)
 */
int sockets::createNonblockingOrDie(sa_family_t family)
{
#if VALGRIND // a kind of memory test tool
	int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
	if (sockfd < 0)
	{
		LOG_SYSFATAL << "sockets::createNonblockingOrDie";
	}
	setNonBlockAndCloseOnExec(sockfd);
#else
	int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
	if (sockfd < 0)
	{
		LOG_SYSFATAL << "sockets::createNonblockingOrDie";
	}
#endif
	return sockfd;
}

void sockets::bindOrDie(int sockfd, const struct sockaddr* addr)
{
	int ret = ::bind(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
	if (ret < 0)
	{
		LOG_SYSFATAL << "sockets::bindOrDie";
	}

#if 0
	// FIXME: added by martin
	// for test
	char buf[128];
	const struct sockaddr_in* addr4 = reinterpret_cast<const struct sockaddr_in*>(addr);
	inet_ntop(addr4->sin_family, &addr4->sin_addr, buf, sizeof(buf));
	LOG_INFO << "sockets::bindOrDie bind " << addr4->sin_family << ", " << addr4->sin_addr.s_addr << ": " << addr4->sin_port;
#endif
}

// Add by Martin Zhang(zhangming_233@163.com)
int sockets::bindNoDie(int sockfd, const sockaddr* addr)
{
	return ::bind(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}

void sockets::listenOrDie(int sockfd)
{
	int ret = ::listen(sockfd, SOMAXCONN);
	if (ret < 0)
	{
		LOG_SYSFATAL << "sockets::listenOrDie";
	}
}

// Add by Martin Zhang(zhangming_233@163.com)
int sockets::listenNoDie(int sockfd)
{
	return ::listen(sockfd, SOMAXCONN);
}

/**
 * accept(2)/accept4(2)包裹函数, 接受连接并获取对端ip地址
 * @param sockfd 服务器sock fd, 指向本地监听的套接字资源
 * @param addr ip地址信息
 * @return 由sockfd接收连接请求得到到连接fd
 */
int sockets::accept(int sockfd, struct sockaddr_in6* addr)
{
	socklen_t addrlen = static_cast<socklen_t>(sizeof(*addr));
#if VALGRIND || defined(NO_ACCEPT4) // VALGRIND: memory check tool
	int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen);
	setNonBlockAndCloseOnExec(connfd);
#else
	// set flags for conn fd returned by accept() at one time
	int connfd = ::accept4(sockfd, sockaddr_cast(addr),
		&addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#endif
	if (connfd < 0)
	{
		int savedErrno = errno;
		LOG_SYSERR << "Socket::accept";
		switch (savedErrno)
		{
		case EAGAIN:
		case ECONNABORTED:
		case EINTR:
		case EPROTO:
		case EMFILE:
			// expected errors
			errno = savedErrno;
			break;
		case EBADF:
		case EFAULT:
		case EINVAL:
		case ENFILE:
		case ENOBUFS:
		case ENOMEM:
		case ENOTSOCK:
		case EOPNOTSUPP:
			// unexpected errors
			LOG_FATAL << "unexpected error of ::accept " << savedErrno;
			break;

		default:
			LOG_FATAL << "unknown error of ::accept " << savedErrno;
			break;
		}
	}
	return connfd;
}

int sockets::connect(int sockfd, const struct sockaddr* addr)
{
	return ::connect(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}

ssize_t sockets::read(int sockfd, void* buf, size_t count)
{
	return ::read(sockfd, buf, count);
}

ssize_t sockets::readv(int sockfd, const struct iovec* iov, int iovcnt)
{
	return ::readv(sockfd, iov, iovcnt);
}

ssize_t sockets::write(int sockfd, const void* buf, size_t count)
{
	return ::write(sockfd, buf, count);
}

void sockets::close(int sockfd)
{
	if (::close(sockfd) < 0)
	{
		LOG_SYSERR << "sockets::close";
	}
}

void sockets::fflush(int sockfd)
{
	//    if (::fflush())
	//    {
	//
	//    }
}

void sockets::shutdownWrite(int sockfd)
{
	if (::shutdown(sockfd, SHUT_WR) < 0)
	{
		LOG_SYSERR << "sockets::shutdownWrite";
	}
}

/**
 * convert struct sockaddr containing ip info to ip string pointed by buf
 * @param buf [out] point to ip string buffer
 * @param size fileSize of buf (bytes)
 * @param addr [in] point to struct sockaddr containing ip address and port info
 * @note port of struct sockaddr is network byte order, but local operation needs
 * host byte order.
 */
void sockets::toIpPort(char* buf, size_t size, const struct sockaddr* addr)
{
	if (addr->sa_family == AF_INET6)
	{ // IPv6
		buf[0] = '[';
		toIp(buf + 1, size - 1, addr);
		size_t end = ::strlen(buf);
		const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
		uint16_t port = sockets::networkToHost16(addr6->sin6_port);
		assert(size > end);
		snprintf(buf + end, size - end, "]:%u", port);
		return;
	}
	// IPv4
	toIp(buf, size, addr);
	size_t end = ::strlen(buf);
	const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
	uint16_t port = sockets::networkToHost16(addr4->sin_port);
	assert(size > end);
	snprintf(buf + end, size - end, ":%u", port);
}

/**
 * convert IP address info from struct sockaddr to string buffer
 * @param buf [out] string buffer with NUL-byte
 * @param size length of string buffer
 * @param addr [in] point to struct sockaddr, which contains ip, port info
 */
void sockets::toIp(char* buf, size_t size,
	const struct sockaddr* addr)
{
	if (addr->sa_family == AF_INET)
	{
		assert(size >= INET_ADDRSTRLEN);
		const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
		::inet_ntop(AF_INET, &addr4->sin_addr, buf, static_cast<socklen_t>(size));
	}
	else if (addr->sa_family == AF_INET6)
	{
		assert(size >= INET6_ADDRSTRLEN);
		const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
		::inet_ntop(AF_INET6, &addr6->sin6_addr, buf, static_cast<socklen_t>(size));
	}
}

/**
 * convert ipv4 string to struct sockaddr_in
 * @param ip ipv4 address string with format like "127.0.0.1"
 * @param port local port for TCP/UDP
 * @param addr [out] store ipv4 info
 */
void sockets::fromIpPort(const char* ip, uint16_t port, struct sockaddr_in* addr)
{
	addr->sin_family = AF_INET;
	addr->sin_port = hostToNetwork16(port);
	if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
	{
		LOG_SYSERR << "sockets::fromIpPort";
	}
}

/**
 * convert ipv6 string to struct socket_in6
 * @param ip ipv6 address string with format like "2409:8a4c:662f:2900:b42c:a0d9:fe5:2037"
 * @param port local port for TCP/UDP
 * @param addr [out] store ipv6 info
 */
void sockets::fromIpPort(const char* ip, uint16_t port, struct sockaddr_in6* addr)
{
	addr->sin6_family = AF_INET6;
	addr->sin6_port = hostToNetwork16(port);
	if (::inet_pton(AF_INET6, ip, &addr->sin6_addr) <= 0)
	{
		LOG_SYSERR << "sockets::fromIpPort";
	}
}

int sockets::getSocketError(int sockfd)
{
	int optval;
	socklen_t optlen = static_cast<socklen_t>(sizeof(optval));

	if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
	{
		return errno;
	}
	else
	{
		return optval;
	}
}

/**
 * Get a local ip address from an opened sock fd
 * @param sockfd an opened sockfd
 * @return local ip address info
 */
struct sockaddr_in6 sockets::getLocalAddr(int sockfd)
{
	struct sockaddr_in6 localaddr;
	memZero(&localaddr, sizeof(localaddr));
	socklen_t addrlen = static_cast<socklen_t>(sizeof(localaddr));
	// get local ip addr info bound to sockfd
	if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0)
	{
		LOG_SYSERR << "sockets::getLocalAddr";
	}
	return localaddr;
}

/**
 * Get a peer ip address from an opened sock fd
 * @param sockfd an opened sockfd
 * @return peer ip address info
 */
struct sockaddr_in6 sockets::getPeerAddr(int sockfd)
{
	struct sockaddr_in6 peeraddr;
	memZero(&peeraddr, sizeof(peeraddr));
	socklen_t addrlen = static_cast<socklen_t>(sizeof(peeraddr));
	if (::getpeername(sockfd, reinterpret_cast<sockaddr*>(&peeraddr), &addrlen))
	{
		LOG_SYSERR << "sockets::getPeerAddr";
	}
	return peeraddr;
}

/**
 * 检查是否为自连接, 判断连接sockfd两端ip地址信息是否相同.
 * @param sockfd 连接对应的文件描述符
 * @return true: 是自连接; false: 不是自连接
 */
bool sockets::isSelfConnect(int sockfd)
{
	struct sockaddr_in6 localaddr = getLocalAddr(sockfd);
	struct sockaddr_in6 peeraddr = getPeerAddr(sockfd);
	if (localaddr.sin6_family == AF_INET)
	{ // IPv4
		const struct sockaddr_in* laddr4 = reinterpret_cast<struct sockaddr_in*>(&localaddr);
		const struct sockaddr_in* raddr4 = reinterpret_cast<struct sockaddr_in*>(&peeraddr);
		return laddr4->sin_port == raddr4->sin_port
			&& laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr;
	}
	else if (localaddr.sin6_family == AF_INET6)
	{ // IPv6
		return localaddr.sin6_port == peeraddr.sin6_port
			&& memcmp(&localaddr.sin6_addr, &peeraddr.sin6_addr, sizeof(localaddr.sin6_addr)) != 0;
	}
	else
	{
		return false;
	}
}
